/* LICENSE BEGIN

Copyright (C) 2007-2008 MySQL AB, 2008 Sun Microsystems, Inc

Copyright (c) 2010 Ysj.Ray

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

 LICENSE END */


#include "network-mysqld-python.h"
#include "pyproxy-plugin.h"
#include "pytypes.h"


#ifdef HAVE_SYS_FILIO_H
/**
 * required for FIONREAD on solaris
 */
#include <sys/filio.h>
#endif

#include <dlfcn.h>

#ifndef _WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include <stdio.h>

#include <errno.h>

#include <glib.h>

/* for solaris 2.5 and NetBSD 1.3.x */
#ifndef HAVE_SOCKLEN_T
typedef int socklen_t;
#endif


#include <mysqld_error.h> /** for ER_UNKNOWN_ERROR */

#include "network-mysqld.h"
#include "network-mysqld-proto.h"
#include "network-mysqld-packet.h"

#include "network-conn-pool.h"

#include "sys-pedantic.h"
#include "network-injection.h"
#include "network-backend.h"
#include "glib-ext.h"

#include "chassis-timings.h"
#include "chassis-mainloop.h"


#define C(x) x, sizeof(x) - 1
#define S(x) x->str, x->len

#define CHECK_FUNC(func) (con->config->proxy_funcs && \
            con->config->proxy_funcs->func)
#define GET_FUNC(func) (con->config->proxy_funcs->func)

#define PLUGIN_NAME "pyproxy"
#define PLUGIN_VERSION "0.7.0"

#define DEFAULT_PROXY_ADDRESS ":2046"
#define DEFAULT_BACKEND_ADDRESS "127.0.0.1:3306"
#define PYTHON_LIB_PATH "/lib/mysql-proxy/python"

#define GET_PYTHON_RESULT(name) \
    if(!result){\
        PyErr_Print();\
        PyErr_Clear();\
        g_critical("PyProxy: Call " #name "() error. See the error log.");\
        network_mysqld_con_send_error(con->client,\
                C("PyProxy: Call " #name "() failed. Check the error log."));\
		con->state = CON_STATE_SEND_ERROR;\
		return PROXY_SEND_RESULT;\
    }\
    if(result == Py_None){}\
	else if(!PyInt_Check(result)){\
		Py_DECREF(result);\
        g_critical("PyProxy: " #name "() return value invalid.");\
        network_mysqld_con_send_error(con->client,\
            C("PyProxy: " #name "() return value invalid."));\
		con->state = CON_STATE_SEND_ERROR;\
		return PROXY_SEND_RESULT;\
    }\
	else \
		ret = (network_mysqld_python_stmt_ret)PyInt_AsLong(result);\
	Py_DECREF(result);\


static network_mysqld_python_stmt_ret
proxy_python_read_query_result(network_mysqld_con *con) {
	network_socket *send_sock = con->client;
	network_socket *recv_sock = con->server;
	injection *inj = NULL;
	network_mysqld_con_python_t *st = con->plugin_con_state;
	network_mysqld_python_stmt_ret ret = PROXY_NO_DECISION;
	GString *packet;

	/**
	 * check if we want to forward the statement to the client
	 * if not, clean the send-queue
	 */
	if (0 == st->injected.queries->length)
		return PROXY_NO_DECISION;

    if(!CHECK_FUNC(read_query_result))
        return ret;

	inj = g_queue_pop_head(st->injected.queries);

	inj->result_queue = con->server->recv_queue->chunks;
	PyObject * injection_obj = Injection_New(inj);
	if(!injection_obj){
		PyErr_Print();
        network_mysqld_con_send_error(con->client,
                    C("PyProxy: Failed to create injection object."));
		con->state = CON_STATE_SEND_ERROR;
        return PROXY_SEND_RESULT;
	}
    PyObject *result = PyObject_CallFunctionObjArgs(
			GET_FUNC(read_query_result), st->proxy, injection_obj, NULL);
	Py_DECREF(injection_obj);
	GET_PYTHON_RESULT(read_query_result)

    if (!con->resultset_is_needed && (PROXY_NO_DECISION != ret)) {
        g_critical("%s: read_query_result() in %s tries to modify the resultset, "
				"but hasn't asked to buffer it in proxy.query.append(..., True). "
				"We ignore the change to the result-set.",
				G_STRLOC, con->config->python_script);
        ret = PROXY_NO_DECISION;
    }

    switch (ret) {
    case PROXY_SEND_RESULT:
		/* we can only replace the result, if we buffer it */
        g_assert_cmpint(con->resultset_is_needed, ==, TRUE);
        while ((packet = g_queue_pop_head(recv_sock->recv_queue->chunks)))
			g_string_free(packet, TRUE);
        if (network_mysqld_con_python_handle_proxy_response(con, st->proxy))
            if (!st->injected.sent_resultset)
                network_mysqld_con_send_error(con->client, C("(python) handling"
								" proxy.response failed, check error-log"));
        /* fall through */
    case PROXY_NO_DECISION:
        if (!st->injected.sent_resultset) {
            // make sure we send only one result-set per client-query
            while ((packet = g_queue_pop_head(recv_sock->recv_queue->chunks)))
                network_mysqld_queue_append_raw(send_sock,
							send_sock->send_queue, packet);
            st->injected.sent_resultset++;
            break;
        }
        g_critical("%s.%d: got asked to send a resultset, but ignoring it as "
					"we already have sent %d resultset(s). injection-id: %d",
					__FILE__, __LINE__, st->injected.sent_resultset, inj->id);
        st->injected.sent_resultset++;

        /* fall through */
    case PROXY_IGNORE_RESULT:
        /* trash the packets for the injection query */

        if (!con->resultset_is_needed) {
            g_critical("%s: we tried to send more than one resultset to the "
						"client, but didn't had them buffered. Now the client "
						"is out of sync may have closed the connection on us. "
						"Please use proxy.queries.append(..., True) to fix this.",
						G_STRLOC);
            break;
        }
        while ((packet = g_queue_pop_head(recv_sock->recv_queue->chunks)))
			g_string_free(packet, TRUE);
        break;
    default:
        /* invalid return code */
        g_message("%s.%d: return-code for read_query_result() was neither "
				"PROXY_SEND_RESULT or PROXY_IGNORE_RESULT, will ignore the result",
                __FILE__, __LINE__);
        while ((packet = g_queue_pop_head(send_sock->send_queue->chunks)))
			g_string_free(packet, TRUE);
        break;
    }
	injection_free(inj);
	return ret;
}

/**
 * call the python function to intercept the handshake packet
 *
 * @return PROXY_SEND_QUERY  to send the packet from the client
 *         PROXY_NO_DECISION to pass the server packet unmodified
 */
static network_mysqld_python_stmt_ret
proxy_python_read_handshake(network_mysqld_con *con) {
	network_mysqld_con_python_t *st = con->plugin_con_state;
	network_mysqld_python_stmt_ret ret = PROXY_NO_DECISION;

    if(!CHECK_FUNC(read_handshake))
        return ret;

    PyObject *result = PyObject_CallFunctionObjArgs(
				GET_FUNC(read_handshake), st->proxy, NULL);
	GET_PYTHON_RESULT(read_handshake)

    switch(ret) {
    case PROXY_NO_DECISION:
        break;
    case PROXY_SEND_QUERY:
        g_warning("%s.%d: (read_handshake) return proxy.PROXY_SEND_QUERY is "
					"deprecated, use PROXY_SEND_RESULT instead",
					__FILE__, __LINE__);
        ret = PROXY_SEND_RESULT;
    case PROXY_SEND_RESULT:
        if(network_mysqld_con_python_handle_proxy_response(con, st->proxy))
            network_mysqld_con_send_error(con->client, C("(python) handling "
							"proxy.response failed, check error-log"));
        break;
    default:
        ret = PROXY_NO_DECISION;
        break;
    }
    return ret;
}


/**
 * parse the hand-shake packet from the server
 *
 *
 * @note the SSL and COMPRESS flags are disabled as we can't 
 *       intercept or parse them.
 */
NETWORK_MYSQLD_PLUGIN_PROTO(proxy_read_handshake) {
	network_packet packet;
	network_socket *recv_sock, *send_sock;
	network_mysqld_auth_challenge *challenge;
	GString *challenge_packet;
	guint8 status = 0;
	int err = 0;

	send_sock = con->client;
	recv_sock = con->server;

 	packet.data = g_queue_peek_tail(recv_sock->recv_queue->chunks);
	packet.offset = 0;

	err = err || network_mysqld_proto_skip_network_header(&packet);
	if(err)
		return NETWORK_SOCKET_ERROR;

	err = err || network_mysqld_proto_peek_int8(&packet, &status);
	if(err)
		return NETWORK_SOCKET_ERROR;

	/* handle ERR packets directly */
	if (status == 0xff) {
		/* move the chunk from one queue to the next */
		network_mysqld_queue_append_raw(send_sock, send_sock->send_queue,
					g_queue_pop_tail(recv_sock->recv_queue->chunks));

		return NETWORK_SOCKET_ERROR; /* it sends what is in the send-queue and hangs up */
	}

	challenge = network_mysqld_auth_challenge_new();
	if (network_mysqld_proto_get_auth_challenge(&packet, challenge)) {
 		g_string_free(g_queue_pop_tail(recv_sock->recv_queue->chunks), TRUE);
		network_mysqld_auth_challenge_free(challenge);
		return NETWORK_SOCKET_ERROR;
	}

 	con->server->challenge = challenge;

	/* we can't sniff compressed packets nor do we support SSL */
	challenge->capabilities &= ~(CLIENT_COMPRESS);
	challenge->capabilities &= ~(CLIENT_SSL);

	switch (proxy_python_read_handshake(con)) {
	case PROXY_NO_DECISION:
		break;
	case PROXY_SEND_RESULT:
 		g_string_free(g_queue_pop_tail(recv_sock->recv_queue->chunks), TRUE);
		return NETWORK_SOCKET_ERROR;
	default:
		g_error("%s.%d: ...", __FILE__, __LINE__);
		break;
	}
	/* the packet we generate will be likely as large as the old one.
	 * should save some reallocs.
	 */
	challenge_packet = g_string_sized_new(packet.data->len);
	network_mysqld_proto_append_auth_challenge(challenge_packet, challenge);
	network_mysqld_queue_sync(send_sock, recv_sock);
	network_mysqld_queue_append(send_sock, send_sock->send_queue, S(challenge_packet));

	g_string_free(challenge_packet, TRUE);
	g_string_free(g_queue_pop_tail(recv_sock->recv_queue->chunks), TRUE);

	/* copy the pack to the client */
	con->state = CON_STATE_SEND_HANDSHAKE;

	return NETWORK_SOCKET_SUCCESS;
}

static network_mysqld_python_stmt_ret
proxy_python_read_auth(network_mysqld_con *con) {
	network_mysqld_python_stmt_ret ret = PROXY_NO_DECISION;
	network_mysqld_con_python_t *st = con->plugin_con_state;

    if(!CHECK_FUNC(read_auth))
        return ret;

    PyObject *result = PyObject_CallFunctionObjArgs(
				GET_FUNC(read_auth), st->proxy, NULL);
	GET_PYTHON_RESULT(read_auth)

    switch(ret) {
    case PROXY_NO_DECISION:
        break;
    case PROXY_SEND_RESULT:
        /* answer directly */
        if (network_mysqld_con_python_handle_proxy_response(con, st->proxy))
            network_mysqld_con_send_error(con->client, C("(python) handling "
							"proxy.response failed, check error-log"));
        break;
    case PROXY_SEND_QUERY:
        /* something is in the injection queue, pull it from there and replace
		 * the content of original packet
		 */

        if (st->injected.queries->length)
            ret = PROXY_SEND_INJECTION;
        else
            ret = PROXY_NO_DECISION;
        break;
    default:
        ret = PROXY_NO_DECISION;
        break;
    }
	return ret;
}

NETWORK_MYSQLD_PLUGIN_PROTO(proxy_read_auth) {
	network_packet packet;
	network_socket *recv_sock, *send_sock;
	chassis_plugin_config *config = con->config;
	network_mysqld_auth_response *auth;
	int err = 0;
	gboolean free_client_packet = TRUE;
	network_mysqld_con_python_t *st = con->plugin_con_state;

	recv_sock = con->client;
	send_sock = con->server;

 	packet.data = g_queue_peek_tail(recv_sock->recv_queue->chunks);
	packet.offset = 0;

	err = err || network_mysqld_proto_skip_network_header(&packet);
	if(err)
		return NETWORK_SOCKET_ERROR;

	auth = network_mysqld_auth_response_new();
	err = err || network_mysqld_proto_get_auth_response(&packet, auth);

	if (err) {
		network_mysqld_auth_response_free(auth);
		return NETWORK_SOCKET_ERROR;
	}
	if (!(auth->capabilities & CLIENT_PROTOCOL_41)) {
		/* should use packet-id 0 */
		network_mysqld_queue_append(con->client, con->client->send_queue,
					C("\xff\xd7\x07" "4.0 protocol is not supported"));
		return NETWORK_SOCKET_ERROR;
	}

 	con->client->response = auth;
	g_string_assign_len(con->client->default_db, S(auth->database));

	/**
	 * looks like we finished parsing, call the python function
	 */
	switch (proxy_python_read_auth(con)) {
	case PROXY_SEND_RESULT:
		con->state = CON_STATE_SEND_AUTH_RESULT;
		break;
	case PROXY_SEND_INJECTION: {
		injection *inj;
		/* replace the client challenge that is sent to the server */
		inj = g_queue_pop_head(st->injected.queries);
		network_mysqld_queue_append(send_sock, send_sock->send_queue,
					S(inj->query));
		injection_free(inj);
		con->state = CON_STATE_SEND_AUTH;
		break; }
	case PROXY_NO_DECISION:
		//if we don't have a backend (con->server), we just ack the client auth
		if (!con->server) {
			con->state = CON_STATE_SEND_AUTH_RESULT;
			network_mysqld_con_send_ok(recv_sock);
			break;
		}
		/* if the server-side of the connection is already up and authed
		 * we send a COM_CHANGE_USER to reauth the connection and remove
		 * all temp-tables and session-variables
		 *
		 * for performance reasons this extra reauth can be disabled. But
		 * that leaves temp-tables on the connection.
		 */
		if (con->server->is_authed) {
			if (config->pool_change_user) {
				GString *com_change_user = g_string_new(NULL);

				/* copy incl. the nul */
				g_string_append_c(com_change_user, COM_CHANGE_USER);
				g_string_append_len(com_change_user,
							con->client->response->username->str,
							con->client->response->username->len + 1);

				g_assert_cmpint(con->client->response->response->len, <, 250);

				g_string_append_c(com_change_user,
							(con->client->response->response->len & 0xff));
				g_string_append_len(com_change_user,
							S(con->client->response->response));

				g_string_append_len(com_change_user,
							con->client->default_db->str,
							con->client->default_db->len + 1);
				network_mysqld_queue_append(send_sock, send_sock->send_queue,
							S(com_change_user));
				/**
				 * the server is already authenticated, the client isn't
				 * transform the auth-packet into a COM_CHANGE_USER
				 */
				g_string_free(com_change_user, TRUE);
				con->state = CON_STATE_SEND_AUTH;
			} else {
				GString *auth_resp;

				/* check if the username and client-scramble are the same as
				 *  in the previous authed connection
				 */
				auth_resp = g_string_new(NULL);
				con->state = CON_STATE_SEND_AUTH_RESULT;

				if (!g_string_equal(con->client->response->username,
								con->server->response->username) ||
				    !g_string_equal(con->client->response->response,
						con->server->response->response)) {
					network_mysqld_err_packet_t *err_packet;

					err_packet = network_mysqld_err_packet_new();
					g_string_assign_len(err_packet->errmsg,
								C("(proxy-pool) login failed"));
					g_string_assign_len(err_packet->sqlstate, C("28000"));
					err_packet->errcode = ER_ACCESS_DENIED_ERROR;

					network_mysqld_proto_append_err_packet(auth_resp, err_packet);
					network_mysqld_err_packet_free(err_packet);
				} else {
					network_mysqld_ok_packet_t *ok_packet;

					ok_packet = network_mysqld_ok_packet_new();
					ok_packet->server_status = SERVER_STATUS_AUTOCOMMIT;

					network_mysqld_proto_append_ok_packet(auth_resp, ok_packet);
					network_mysqld_ok_packet_free(ok_packet);
				}

				network_mysqld_queue_append(recv_sock, recv_sock->send_queue,
						S(auth_resp));

				g_string_free(auth_resp, TRUE);
			}
		} else {
			network_mysqld_queue_append_raw(send_sock, send_sock->send_queue,
						packet.data);
			con->state = CON_STATE_SEND_AUTH;
			/* the packet.data is now part of the send-queue,
			 * don't free it further down
			 */
			free_client_packet = FALSE;
		}

		break;
	default:
		g_assert_not_reached();
		break;
	}

	if (free_client_packet) {
		g_string_free(g_queue_pop_tail(recv_sock->recv_queue->chunks), TRUE);
	} else {
		/* just remove the link to the packet, the packet itself is part
		 * of the next queue already
		 */
		g_queue_pop_tail(recv_sock->recv_queue->chunks);
	}

	return NETWORK_SOCKET_SUCCESS;
}

static network_mysqld_python_stmt_ret
proxy_python_read_auth_result(network_mysqld_con *con) {
	network_mysqld_python_stmt_ret ret = PROXY_NO_DECISION;
	network_mysqld_con_python_t *st = con->plugin_con_state;

	network_socket *recv_sock = con->server;
	GList *chunk = recv_sock->recv_queue->chunks->tail;
	GString *packet = chunk->data;

    if(!CHECK_FUNC(read_auth_result))
        return ret;

	PyObject *auth = Auth_New(packet->str + NET_HEADER_SIZE,
				packet->len - NET_HEADER_SIZE);
	if(!auth){
        g_critical("PyProxy: Failed to create the auth object.");
        PyErr_Print();
		PyErr_Clear();
		network_mysqld_con_send_error(con->client,
                    C("PyProxy: Failed to create auth object."));
		con->state = CON_STATE_SEND_ERROR;
		return PROXY_SEND_RESULT;
	}
    PyObject *result = PyObject_CallFunctionObjArgs(
                GET_FUNC(read_auth_result), st->proxy, auth, NULL);
	Py_DECREF(auth);
	GET_PYTHON_RESULT(read_auth_result)

    switch(ret){
    case PROXY_NO_DECISION:
        break;
    case PROXY_SEND_RESULT:
        if (network_mysqld_con_python_handle_proxy_response(con, st->proxy))
            network_mysqld_con_send_error(con->client, C("(python) handling "
							"proxy.response failed, check error-log"));
        break;
    default:
        ret = PROXY_NO_DECISION;
        break;
    }
	return ret;
}


NETWORK_MYSQLD_PLUGIN_PROTO(proxy_read_auth_result) {
	GString *packet;
	GList *chunk;
	network_socket *recv_sock, *send_sock;

	recv_sock = con->server;
	send_sock = con->client;

	chunk = recv_sock->recv_queue->chunks->tail;
	packet = chunk->data;

	/* send the auth result to the client */
	if (con->server->is_authed) {
		/**
		 * we injected a COM_CHANGE_USER above and have to correct to
		 * packet-id now
		 */
		packet->str[3] = 2;
	}
	/**
	 * copy the
	 * - default-db,
	 * - username,
	 * - scrambed_password
	 *
	 * to the server-side
	 */
	g_string_assign_len(recv_sock->default_db, S(send_sock->default_db));

	con->server->response =
		network_mysqld_auth_response_copy(con->client->response);

	/**
	 * recv_sock still points to the old backend that
	 * we received the packet from.
	 * backend_ndx = -1 might have reset con->server
	 */

	switch (proxy_python_read_auth_result(con)) {
	case PROXY_SEND_RESULT:
		/**
		 * we already have content in the send-sock
		 * chunk->packet is not forwarded, free it
		 */
		g_string_free(packet, TRUE);
		break;
	case PROXY_NO_DECISION:
		network_mysqld_queue_append_raw(send_sock, send_sock->send_queue, packet);
		break;
	default:
		g_error("%s.%d: ... ", __FILE__, __LINE__);
		break;
	}
	/**
	 * we handled the packet on the server side, free it
	 */
	g_queue_delete_link(recv_sock->recv_queue->chunks, chunk);
	/* the auth phase is over
	 *
	 * reset the packet-id sequence
	 */
	network_mysqld_queue_reset(send_sock);
	network_mysqld_queue_reset(recv_sock);
	con->state = CON_STATE_SEND_AUTH_RESULT;

	return NETWORK_SOCKET_SUCCESS;
}

static network_mysqld_python_stmt_ret
proxy_python_read_query(network_mysqld_con *con) {
	network_mysqld_con_python_t *st = con->plugin_con_state;
	char command = -1;
	network_socket *recv_sock = con->client;
	GList   *chunk  = recv_sock->recv_queue->chunks->head;
	GString *packet = chunk->data;
	chassis_plugin_config *config = con->config;
    network_mysqld_python_stmt_ret ret = PROXY_NO_DECISION;

	if (!config->profiling)
		return PROXY_SEND_QUERY;
	if (packet->len <= NET_HEADER_SIZE)
		return PROXY_SEND_QUERY;
	if (con->in_load_data_local_state)
		return PROXY_SEND_QUERY;

	command = packet->str[NET_HEADER_SIZE + 0];

	if (COM_QUERY == command) {
		if (packet->len < NET_HEADER_SIZE + 2)
			return PROXY_SEND_QUERY;

		if (packet->len - NET_HEADER_SIZE - 1 >= sizeof("LOAD ") - 1 &&
				0 == g_ascii_strncasecmp(packet->str + NET_HEADER_SIZE + 1,
					C("LOAD ")))
			return PROXY_SEND_QUERY;

		if (packet->len - NET_HEADER_SIZE - 1 >= sizeof("SHOW ERRORS") - 1 &&
				0 == g_ascii_strncasecmp(packet->str + NET_HEADER_SIZE + 1,
					C("SHOW ERRORS")))
			return PROXY_SEND_QUERY;
		if (packet->len - NET_HEADER_SIZE - 1 >= sizeof("select @@error_count")-1
				&& 0 == g_ascii_strncasecmp(packet->str + NET_HEADER_SIZE + 1,
					C("select @@error_count")))
			return PROXY_SEND_QUERY;
	}
	network_injection_queue_reset(st->injected.queries);

    if(!CHECK_FUNC(read_query))
        return ret;

	GString *chunks = g_string_new("");
	int i = 0;
	for(i = 0; NULL != (packet = g_queue_peek_nth(
						recv_sock->recv_queue->chunks, i)); i++)
		g_string_append_len(chunks, packet->str + NET_HEADER_SIZE,
					packet->len - NET_HEADER_SIZE);

	PyObject *whole_packet = PyString_FromString(chunks->str);
	if(!whole_packet){
		network_mysqld_con_send_error(con->client, C("(python) create whole "
						"packet failed."));
		return ret;
	}
    PyObject *result = PyObject_CallFunctionObjArgs(
                GET_FUNC(read_query), st->proxy, whole_packet, NULL);
	Py_DECREF(whole_packet);
	GET_PYTHON_RESULT(read_query)

    switch (ret) {
    case PROXY_SEND_RESULT:
        if (network_mysqld_con_python_handle_proxy_response(con, st->proxy))
            network_mysqld_con_send_error(con->client, C("(python) handling "
							"proxy.response failed, check error-log"));
        break;
    case PROXY_NO_DECISION:
        break;
    case PROXY_SEND_QUERY:
        if (st->injected.queries->length)
            ret = PROXY_SEND_INJECTION;
        break;
    default:
        break;
    }
	return ret;
}

/**
 * gets called after a query has been read
 *
 * - calls the python script via network_mysqld_con_handle_proxy_stmt()
 *
 * @see network_mysqld_con_handle_proxy_stmt
 */
NETWORK_MYSQLD_PLUGIN_PROTO(proxy_read_query) {
	GString *packet;
	network_socket *recv_sock, *send_sock;
	network_mysqld_con_python_t *st = con->plugin_con_state;
	int proxy_query = 1;
	network_mysqld_python_stmt_ret ret;

	NETWORK_MYSQLD_CON_TRACK_TIME(con, "proxy::ready_query::enter");

	send_sock = NULL;
	recv_sock = con->client;
	st->injected.sent_resultset = 0;

	NETWORK_MYSQLD_CON_TRACK_TIME(con, "proxy::ready_query::enter_python");
	ret = proxy_python_read_query(con);
	NETWORK_MYSQLD_CON_TRACK_TIME(con, "proxy::ready_query::leave_python");

	/**
	 * if we disconnected in read_query_result() we have no connection open
	 * when we try to execute the next query
	 *
	 * for PROXY_SEND_RESULT we don't need a server
	 */
	if (ret != PROXY_SEND_RESULT && con->server == NULL) {
			g_critical("%s.%d: I have no server backend, closing connection",
					__FILE__, __LINE__);
		return NETWORK_SOCKET_ERROR;
	}

	send_sock = con->server;

	switch (ret) {
	case PROXY_NO_DECISION:
	case PROXY_SEND_QUERY:
		/* no injection, pass on the chunks as is */
		while ((packet = g_queue_pop_head(recv_sock->recv_queue->chunks)))
			network_mysqld_queue_append_raw(send_sock, send_sock->send_queue,
						packet);
		/* we don't want to buffer the result-set */
		con->resultset_is_needed = FALSE;
		break;
	case PROXY_SEND_RESULT:
		proxy_query = 0;
		while ((packet = g_queue_pop_head(recv_sock->recv_queue->chunks)))
			g_string_free(packet, TRUE);
		break;
	case PROXY_SEND_INJECTION: {
		injection *inj;

		inj = g_queue_peek_head(st->injected.queries);
		/* let the python-layer decide if we want to buffer the result or not */
		con->resultset_is_needed = inj->resultset_is_needed;

		network_mysqld_queue_reset(send_sock);
		network_mysqld_queue_append(send_sock, send_sock->send_queue,
					S(inj->query));

		while ((packet = g_queue_pop_head(recv_sock->recv_queue->chunks)))
			g_string_free(packet, TRUE);

		break; }
	default:
		g_error("%s.%d: ", __FILE__, __LINE__);
	}

	if (proxy_query)
		con->state = CON_STATE_SEND_QUERY;
	else {
		con->state = CON_STATE_SEND_QUERY_RESULT;
		con->resultset_is_finished = TRUE; /* we don't have more too send */
	}
	NETWORK_MYSQLD_CON_TRACK_TIME(con, "proxy::ready_query::done");

	return NETWORK_SOCKET_SUCCESS;
}

/**
 * decide about the next state after the result-set has been written
 * to the client
 *
 * if we still have data in the queue, back to proxy_send_query()
 * otherwise back to proxy_read_query() to pick up a new client query
 *
 * @note we should only send one result back to the client
 */
NETWORK_MYSQLD_PLUGIN_PROTO(proxy_send_query_result) {
	network_socket *recv_sock, *send_sock;
	injection *inj;
	network_mysqld_con_python_t *st = con->plugin_con_state;

	send_sock = con->server;
	recv_sock = con->client;

	if (st->connection_close) {
		con->state = CON_STATE_ERROR;

		return NETWORK_SOCKET_SUCCESS;
	}

	if (con->parse.command == COM_BINLOG_DUMP) {
		/**
		 * the binlog dump is different as it doesn't have END packet
		 *
		 * @todo in 5.0.x a NON_BLOCKING option as added which sends a EOF

		 */
		con->state = CON_STATE_READ_QUERY_RESULT;

		return NETWORK_SOCKET_SUCCESS;
	}

	/* if we don't have a backend, don't try to forward queries
	 */
	if (!send_sock) {
		network_injection_queue_reset(st->injected.queries);
	}

	if (st->injected.queries->length == 0) {
		con->state = CON_STATE_READ_QUERY;

		return NETWORK_SOCKET_SUCCESS;
	}

	/* looks like we still have queries in the queue,
	 * push the next one
	 */
	inj = g_queue_peek_head(st->injected.queries);
	con->resultset_is_needed = inj->resultset_is_needed;

	if (!inj->resultset_is_needed && st->injected.sent_resultset > 0) {
		/* we already sent a resultset to the client and the next query wants
		 * to forward it's result-set too, that can't work
		 */
		g_critical("%s: proxy.queries:append() in %s can only have one injected "
					"query without { resultset_is_needed = true } set. We close "
					"the client connection now.",
				G_STRLOC, con->config->python_script);

		return NETWORK_SOCKET_ERROR;
	}

	g_assert(inj);
	g_assert(send_sock);

	network_mysqld_queue_reset(send_sock);
	network_mysqld_queue_append(send_sock, send_sock->send_queue, S(inj->query));

	network_mysqld_con_reset_command_response_state(con);
	con->state = CON_STATE_SEND_QUERY;

	return NETWORK_SOCKET_SUCCESS;
}

/**
 * handle the query-result we received from the server
 *
 * - decode the result-set to track if we are finished already
 * - handles BUG#25371 if requested
 * - if the packet is finished, calls the network_mysqld_con_handle_proxy_resultset
 *   to handle the resultset in the python-scripts
 *
 * @see network_mysqld_con_handle_proxy_resultset
 */
NETWORK_MYSQLD_PLUGIN_PROTO(proxy_read_query_result) {
	int is_finished = 0;
	network_packet packet;
	network_socket *recv_sock, *send_sock;
	network_mysqld_con_python_t *st = con->plugin_con_state;
	injection *inj = NULL;

	NETWORK_MYSQLD_CON_TRACK_TIME(con, "proxy::ready_query_result::enter");

	recv_sock = con->server;
	send_sock = con->client;

	/* check if the last packet is valid */
	packet.data = g_queue_peek_tail(recv_sock->recv_queue->chunks);
	packet.offset = 0;

	if (0 != st->injected.queries->length)
		inj = g_queue_peek_head(st->injected.queries);

	if (inj && inj->ts_read_query_result_first == 0)
		inj->ts_read_query_result_first = chassis_get_rel_microseconds();

	is_finished = network_mysqld_proto_get_query_result(&packet, con);
	/* something happend, let's get out of here */
	if (is_finished == -1)
		return NETWORK_SOCKET_ERROR;

	con->resultset_is_finished = is_finished;

	/* copy the packet over to the send-queue if we don't need it */
	if (!con->resultset_is_needed)
		network_mysqld_queue_append_raw(send_sock, send_sock->send_queue,
					g_queue_pop_tail(recv_sock->recv_queue->chunks));

	if (is_finished) {
		if (inj) {
			if (con->parse.command == COM_QUERY ||
						con->parse.command == COM_STMT_EXECUTE) {
				network_mysqld_com_query_result_t *com_query = con->parse.data;

				inj->bytes = com_query->bytes;
				inj->rows  = com_query->rows;
				inj->qstat.was_resultset = com_query->was_resultset;
				inj->qstat.binary_encoded = com_query->binary_encoded;

				/* INSERTs have a affected_rows */
				if (!com_query->was_resultset) {
					inj->qstat.affected_rows = com_query->affected_rows;
					inj->qstat.insert_id     = com_query->insert_id;
				}
				inj->qstat.server_status = com_query->server_status;
				inj->qstat.warning_count = com_query->warning_count;
				inj->qstat.query_status  = com_query->query_status;
			}
			inj->ts_read_query_result_last = chassis_get_rel_microseconds();
		}

		network_mysqld_queue_reset(recv_sock);

		NETWORK_MYSQLD_CON_TRACK_TIME(con, "proxy::ready_query_result::enter_python");
		proxy_python_read_query_result(con);
		NETWORK_MYSQLD_CON_TRACK_TIME(con, "proxy::ready_query_result::leave_python");

		/** recv_sock might be != con->server now */
		network_mysqld_queue_reset(send_sock);

		/**
		 * if the send-queue is empty, we have nothing to send
		 * and can read the next query */
		if (send_sock->send_queue->chunks)
			con->state = CON_STATE_SEND_QUERY_RESULT;
		else {
			/* we already forwarded the resultset, no way someone has flushed
			 * the resultset-queue
			 */
			g_assert_cmpint(con->resultset_is_needed, ==, 1);
			con->state = CON_STATE_READ_QUERY;
		}
	}
	NETWORK_MYSQLD_CON_TRACK_TIME(con, "proxy::ready_query_result::leave");
	return NETWORK_SOCKET_SUCCESS;
}


static network_mysqld_python_stmt_ret
proxy_python_connect_server(network_mysqld_con *con) {
	network_mysqld_python_stmt_ret ret = PROXY_NO_DECISION;
	network_mysqld_con_python_t *st = con->plugin_con_state;

    if(!CHECK_FUNC(connect_server))
        return ret;

    PyObject *result = PyObject_CallFunctionObjArgs(
                GET_FUNC(connect_server), st->proxy, NULL);
	GET_PYTHON_RESULT(connect_server)

    switch(ret){
    case PROXY_NO_DECISION:
    case PROXY_IGNORE_RESULT:
        break;
    case PROXY_SEND_RESULT:
        //When return PROXY_SEND_RESULT, script's connect_server()
        //must set proxy.response to apporiate value. Check it.
        if(network_mysqld_con_python_handle_proxy_response(con, st->proxy))
            network_mysqld_con_send_error(con->client, C("(python) handling "
						"proxy.response failed, check error-log"));
        break;
    default:
        ret = PROXY_NO_DECISION;
        break;
    }
	return ret;
}


/**
 * connect to a backend
 *
 * @return
 *   NETWORK_SOCKET_SUCCESS        - connected successfully
 *   NETWORK_SOCKET_ERROR_RETRY    - connecting backend failed, call again to connect to another backend
 *   NETWORK_SOCKET_ERROR          - no backends available, adds a ERR packet to the client queue
 */
NETWORK_MYSQLD_PLUGIN_PROTO(proxy_connect_server) {
	network_mysqld_con_python_t *st = con->plugin_con_state;
	chassis_private *g = con->srv->priv;
	guint min_connected_clients = G_MAXUINT;
	guint i;
	gboolean use_pooled_connection = FALSE;
	network_backend_t *cur;

	if (con->server) {
		switch (network_socket_connect_finish(con->server)) {
		case NETWORK_SOCKET_SUCCESS:
			break;
		case NETWORK_SOCKET_ERROR:
		case NETWORK_SOCKET_ERROR_RETRY:
			g_message("%s.%d: connect(%s) failed: %s. Retrying with different backend.", 
					__FILE__, __LINE__,
					con->server->dst->name->str, g_strerror(errno));

			/* mark the backend as being DOWN and retry with a different one */
			st->backend->state = BACKEND_STATE_DOWN;
			g_get_current_time(&(st->backend->state_since));
			network_socket_free(con->server);
			con->server = NULL;

			return NETWORK_SOCKET_ERROR_RETRY;
		default:
			g_assert_not_reached();
			break;
		}

		if (st->backend->state != BACKEND_STATE_UP) {
			st->backend->state = BACKEND_STATE_UP;
			g_get_current_time(&(st->backend->state_since));
		}

		con->state = CON_STATE_READ_HANDSHAKE;

		return NETWORK_SOCKET_SUCCESS;
	}

	st->backend = NULL;
	st->backend_ndx = -1;

	network_backends_check(g->backends);

	switch (proxy_python_connect_server(con)) {
	case PROXY_SEND_RESULT:
		return NETWORK_SOCKET_SUCCESS;
	case PROXY_NO_DECISION:
		break;
	case PROXY_IGNORE_RESULT:
		use_pooled_connection = TRUE;
		break;
	default:
		g_error("%s.%d: ... ", __FILE__, __LINE__);
		break;
	}

	/* protect the typecast below */
	g_assert_cmpint(g->backends->backends->len, <, G_MAXINT);

	/**
	 * if the current backend is down, ignore it
	 */
	cur = network_backends_get(g->backends, st->backend_ndx);

	if(cur)
		if(cur->state == BACKEND_STATE_DOWN)
			st->backend_ndx = -1;

	if (con->server && !use_pooled_connection) {
		gint bndx = st->backend_ndx;
		/* we already have a connection assigned,
		 * but the script said we don't want to use it
		 */
		network_connection_pool_python_add_connection(con);
		st->backend_ndx = bndx;
	}

	if (st->backend_ndx < 0) {
		/**
		 * we can choose between different back addresses
		 *
		 * prefer SQF (shorted queue first) to load all backends equally
		 */

		for (i = 0; i < network_backends_count(g->backends); i++) {
			cur = network_backends_get(g->backends, i);

			/**
			 * skip backends which are down or not writable
			 */
			if (cur->state == BACKEND_STATE_DOWN ||
			    cur->type != BACKEND_TYPE_RW) continue;

			if (cur->connected_clients < min_connected_clients) {
				st->backend_ndx = i;
				min_connected_clients = cur->connected_clients;
			}
		}

		if ((cur = network_backends_get(g->backends, st->backend_ndx))) {
			st->backend = cur;
		}
	} else if (NULL == st->backend) {
		if ((cur = network_backends_get(g->backends, st->backend_ndx))) {
			st->backend = cur;
		}
	}

	if (NULL == st->backend) {
		network_mysqld_con_send_error(con->client, C("(proxy) all backends "
						"are down"));
		g_critical("%s.%d: Cannot connect, all backends are down.",
					__FILE__, __LINE__);
		return NETWORK_SOCKET_ERROR;
	}

	/**
	 * check if we have a connection in the pool for this backend
	 */
	if (NULL == con->server) {
		con->server = network_socket_new();
		network_address_copy(con->server->dst, st->backend->addr);

		st->backend->connected_clients++;

		switch(network_socket_connect(con->server)) {
		case NETWORK_SOCKET_ERROR_RETRY:
			/* the socket is non-blocking already,
			 * call getsockopt() to see if we are done */
			return NETWORK_SOCKET_ERROR_RETRY;
		case NETWORK_SOCKET_SUCCESS:
			break;
		default:
			g_message("%s.%d: connecting to backend (%s) failed, marking it "
						"as down for ...", __FILE__, __LINE__,
						con->server->dst->name->str);

			st->backend->state = BACKEND_STATE_DOWN;
			g_get_current_time(&(st->backend->state_since));

			network_socket_free(con->server);
			con->server = NULL;

			return NETWORK_SOCKET_ERROR_RETRY;
		}

		if (st->backend->state != BACKEND_STATE_UP) {
			st->backend->state = BACKEND_STATE_UP;
			g_get_current_time(&(st->backend->state_since));
		}

		con->state = CON_STATE_READ_HANDSHAKE;
	} else {
		GString *auth_packet;

		/**
		 * send the old hand-shake packet
		 */
		auth_packet = g_string_new(NULL);
		network_mysqld_proto_append_auth_challenge(auth_packet,
					con->server->challenge);
		network_mysqld_queue_append(con->client, con->client->send_queue,
				S(auth_packet));
		g_string_free(auth_packet, TRUE);
		con->state = CON_STATE_SEND_HANDSHAKE;
	}

	return NETWORK_SOCKET_SUCCESS;
}

NETWORK_MYSQLD_PLUGIN_PROTO(proxy_init) {
	network_mysqld_con_python_t *st = con->plugin_con_state;
	g_assert(con->plugin_con_state == NULL);
	st = network_mysqld_con_python_new();

	if(con->config->proxy_funcs){
		st->proxy = Proxy_New(con);
		if(!st->proxy){
			g_critical("PyProxy: Failed to create the proxy object.");
			PyErr_Print();
			PyErr_Clear();
			return NETWORK_SOCKET_ERROR;
		}
	}

	con->plugin_con_state = st;
	con->state = CON_STATE_CONNECT_SERVER;

	return NETWORK_SOCKET_SUCCESS;
}

static network_mysqld_python_stmt_ret proxy_python_disconnect_client(network_mysqld_con *con) {
	network_mysqld_python_stmt_ret ret = PROXY_NO_DECISION;
	network_mysqld_con_python_t *st = con->plugin_con_state;

    if(!CHECK_FUNC(disconnect_client))
        return ret;

    PyObject *result = PyObject_CallFunctionObjArgs(
                GET_FUNC(disconnect_client), st->proxy, NULL);
	GET_PYTHON_RESULT(disconnect_client)

    switch (ret) {
    case PROXY_NO_DECISION:
    case PROXY_IGNORE_RESULT:
        break;
    default:
        ret = PROXY_NO_DECISION;
        break;
    }

	return ret;
}

/**
 * cleanup the proxy specific data on the current connection
 *
 * move the server connection into the connection pool in case it is a
 * good client-side close
 *
 * @return NETWORK_SOCKET_SUCCESS
 * @see plugin_call_cleanup
 */
NETWORK_MYSQLD_PLUGIN_PROTO(proxy_disconnect_client) {
	network_mysqld_con_python_t *st = con->plugin_con_state;
	gboolean use_pooled_connection = FALSE;

	if (st == NULL) return NETWORK_SOCKET_SUCCESS;

	switch (proxy_python_disconnect_client(con)) {
	case PROXY_NO_DECISION:
		break;
	case PROXY_IGNORE_RESULT:
		break;
	default:
		g_error("%s.%d: ... ", __FILE__, __LINE__);
		break;
	}

	/**
	 * check if one of the backends has to many open connections
	 */
	if (use_pooled_connection && con->state == CON_STATE_CLOSE_CLIENT)
		/* move the connection to the connection pool
		 *
		 * this disconnects con->server and safes it from getting free()ed later
		 */
		network_connection_pool_python_add_connection(con);
	else if (st->backend)
		/* we have backend assigned and want to close the connection to it */
		st->backend->connected_clients--;

	network_mysqld_con_python_free(st);
	con->plugin_con_state = NULL;

	/**
	 * walk all pools and clean them up
	 */
	return NETWORK_SOCKET_SUCCESS;
}

int network_mysqld_pyproxy_connection_init(network_mysqld_con *con) {
	con->plugins.con_init                      = proxy_init;
	con->plugins.con_connect_server            = proxy_connect_server;
	con->plugins.con_read_handshake            = proxy_read_handshake;
	con->plugins.con_read_auth                 = proxy_read_auth;
	con->plugins.con_read_auth_result          = proxy_read_auth_result;
	con->plugins.con_read_query                = proxy_read_query;
	con->plugins.con_read_query_result         = proxy_read_query_result;
	con->plugins.con_send_query_result         = proxy_send_query_result;
	con->plugins.con_cleanup                   = proxy_disconnect_client;

	return 0;
}


static void *dlopen_python(void){
#ifndef _WIN32
	//Here should open the libaray named "libpython2.x.so"
    char *dl_file = "lib" PYTHON_VERSION ".so";
#else
	//Here should open the libaray named "python2x.so"
	gchar **py_dllnames = g_strsplit(PYTHON_VERSION, ".", 3);
	gchar *dl_file = g_strconcat(py_dllnames[0], py_dllnames[1], ".dll");
#endif
    void *handle = dlopen(dl_file, RTLD_LAZY | RTLD_GLOBAL);
    if(!handle){
        g_critical("Cannot dlopen %s\n", dl_file);
    }
#ifdef _WIN32
	g_free(dl_file);
	g_strfreev(py_dllnames);
#endif
    return handle;
}


static void dlclose_python(void *handle){
    if(handle)
        dlclose(handle);
}


chassis_plugin_config * network_mysqld_pyproxy_plugin_new(void) {
	chassis_plugin_config *config;

	config = g_new0(chassis_plugin_config, 1);
	config->fix_bug_25371   = 0; /** double ERR packet on AUTH failures */
	config->profiling       = 1;
	config->start_proxy     = 1;
	config->pool_change_user = 1; /* issue a COM_CHANGE_USER to cleanup the
				 connection when we get back the connection from the pool */

    /**  Fix me! here dynamic load the libpython2.x.so to resolve the shared
	 *   library problem under ubuntu dyna-load mechanisim:
     *   /usr/lib/python2.x/dyna-load/time.so: cannot resolve simble "PyExc_ValueError"
     */
    Py_Initialize();
    config->python_dynalib_handle = dlopen_python();
	return config;
}

void network_mysqld_pyproxy_plugin_free(chassis_plugin_config *config) {
	gsize i;
	if (config->listen_con) {
		/**
		 * the connection will be free()ed by the network_mysqld_free()
		 */
#if 0
		event_del(&(config->listen_con->server->event));
		network_mysqld_con_free(config->listen_con);
#endif
	}

	if (config->backend_addresses) {
		for (i = 0; config->backend_addresses[i]; i++)
			g_free(config->backend_addresses[i]);
		g_free(config->backend_addresses);
	}

	if (config->address)
		g_free(config->address);

	if(config->python_script)
        g_free(config->python_script);
    if(config->proxy_funcs){
        Py_XDECREF(config->proxy_funcs->init);
        Py_XDECREF(config->proxy_funcs->connect_server);
        Py_XDECREF(config->proxy_funcs->read_handshake);
        Py_XDECREF(config->proxy_funcs->read_auth);
        Py_XDECREF(config->proxy_funcs->read_auth_result);
        Py_XDECREF(config->proxy_funcs->read_query);
        Py_XDECREF(config->proxy_funcs->read_query_result);
        Py_XDECREF(config->proxy_funcs->send_query_result);
        Py_XDECREF(config->proxy_funcs->disconnect_client);
        g_free(config->proxy_funcs);
    }
	g_free(config);
    dlclose_python(config->python_dynalib_handle);
    Py_Finalize();
}

/**
 * plugin options
 */
static GOptionEntry *
network_mysqld_pyproxy_plugin_get_options(chassis_plugin_config *config){
	guint i;

	/* make sure it isn't collected */
	static GOptionEntry config_entries[] = {
		{ "pyproxy-address",
					'P', 0, G_OPTION_ARG_STRING, NULL,
					"listening address:port of the pyproxy-server (default: "
					DEFAULT_PROXY_ADDRESS ")", "<host:port>" },
		{ "pyproxy-read-only-backend-addresses",
					'r', 0, G_OPTION_ARG_STRING_ARRAY, NULL,
					"address:port of the remote slave-server (default: not set)",
					"<host:port>" },
		{ "pyproxy-backend-addresses",
					'b', 0, G_OPTION_ARG_STRING_ARRAY, NULL,
					"address:port of the remote backend-servers (default: "
					DEFAULT_BACKEND_ADDRESS ")", "<host:port>" },
		{ "pyproxy-skip-profiling",
					0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, NULL,
					"disables profiling of queries (default: enabled)", NULL },
		{ "pyproxy-python-script",
					's', 0, G_OPTION_ARG_FILENAME, NULL,
					"filename of the python script (default: not set)", "<file>" },
		{ "no-pyproxy",
					0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, NULL,
					"don't start the pyproxy-module (default: enabled)", NULL },
		{ "pyproxy-pool-no-change-user",
					0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, NULL,
					"don't use CHANGE_USER to reset the connection coming from the pool "
					"(default: enabled)",NULL },
		{ NULL, 0, 0, G_OPTION_ARG_NONE,   NULL, NULL, NULL }
	};

	i = 0;
	config_entries[i++].arg_data = &(config->address);
	config_entries[i++].arg_data = &(config->read_only_backend_addresses);
	config_entries[i++].arg_data = &(config->backend_addresses);

	config_entries[i++].arg_data = &(config->profiling);

	config_entries[i++].arg_data = &(config->python_script);
	config_entries[i++].arg_data = &(config->start_proxy);
	config_entries[i++].arg_data = &(config->pool_change_user);

	return config_entries;
}

/**
 * init the plugin with the parsed config.
 * need not gain the GIL.
 */
int network_mysqld_pyproxy_plugin_apply_config(chassis *chas,
			chassis_plugin_config *config) {
    PyObject *path = PySys_GetObject("path");
    if(path){
        GString *s = g_string_new(chas->base_dir);
        g_string_append(s, PYTHON_LIB_PATH);
        PyObject *python_libs = PyString_FromString(s->str);
        if(!python_libs){
            PyErr_Print();
            PyErr_Clear();
            return 0;
        }
        if(PyList_Append(path, python_libs)){
            PyErr_Print();
            PyErr_Clear();
            return 0;
        }
        Py_DECREF(python_libs);
    }

	PyObject *chassis_addr = PyInt_FromLong((int)chas);
	if(!chassis_addr){
		PyErr_Print();
		PyErr_Clear();
		return 0;
	}
	network_mysqld_con *con;
	network_socket *listen_sock;
	chassis_private *g = chas->priv;
	guint i;

	if (!config->start_proxy) {
		return 0;
	}

	if (!config->address)
		config->address = g_strdup(DEFAULT_PROXY_ADDRESS);
	if (!config->backend_addresses) {
		config->backend_addresses = g_new0(char *, 2);
		config->backend_addresses[0] = g_strdup(DEFAULT_BACKEND_ADDRESS);
	}

	/**
	 * create a connection handle for the listen socket
	 */
	con = network_mysqld_con_new();
	network_mysqld_add_connection(chas, con);
	con->config = config;

	config->listen_con = con;

	listen_sock = network_socket_new();
	con->server = listen_sock;

	/** set the plugin hooks as we want to apply them to the new
	 *  connections too later
	 */
	network_mysqld_pyproxy_connection_init(con);

	/* FIXME: network_socket_set_address() */
	if (0 != network_address_set_address(listen_sock->dst, config->address))
		return -1;

	/* FIXME: network_socket_bind() */
	if (0 != network_socket_bind(listen_sock))
		return -1;

	g_message("pyproxy listening on port %s", config->address);

	for (i = 0; config->backend_addresses && config->backend_addresses[i]; i++)
		network_backends_add(g->backends, config->backend_addresses[i],
					BACKEND_TYPE_RW);

	for (i = 0; config->read_only_backend_addresses &&
				config->read_only_backend_addresses[i]; i++)
		network_backends_add(g->backends, config->read_only_backend_addresses[i],
					BACKEND_TYPE_RO);

	/* load the script */
	if(network_mysqld_python_initialize(config))
        return -1;

	// call network_mysqld_con_accept() with this connection when we are done
	event_set(&(listen_sock->event), listen_sock->fd, EV_READ|EV_PERSIST,
				network_mysqld_con_accept, con);
	event_base_set(chas->event_base, &(listen_sock->event));
	event_add(&(listen_sock->event), NULL);

	return 0;
}

G_MODULE_EXPORT int plugin_init(chassis_plugin *p) {
	p->magic        = CHASSIS_PLUGIN_MAGIC;
	p->name         = g_strdup(PLUGIN_NAME);
	p->version		= g_strdup(PLUGIN_VERSION);

	p->init         = network_mysqld_pyproxy_plugin_new;
	p->get_options  = network_mysqld_pyproxy_plugin_get_options;
	p->apply_config = network_mysqld_pyproxy_plugin_apply_config;
	p->destroy      = network_mysqld_pyproxy_plugin_free;

	return 0;
}

